目前我們實現了即時搜尋顯示書名相符的書單資料,但是當我切換到其他路由之後又想再回到上一頁的搜尋結果,或是直接在當前路徑重新整理頁面時,搜尋欄位就被清空了,而書單資料因為搜尋欄位無字串值可供辨別,因此連帶失去篩選條件,一切回到最一開始的樣子。
事實上,網站中的欄位並不會記住使用者的輸入值,甚至是存進 Vuex 的資料也會在刷新頁面之後被清空,因此才需要每次在不同時機進入路由或元件時,都需要發送 API 重新取得資料再渲染到畫面上,又或者是透過暫存在用戶端的 LocalStorage 或 Cookie 以便日後取用。
以為沒有地方可以存取關鍵字,但其實近在眼前就有個可以利用的工具,就是路由!大家可以觀察最常使用的 Google 搜尋,假設我們在搜尋欄位輸入「vue.js」,搜尋結果會導向「 https://www.google.com/search?q=vue.js 」(& 後面暫且略過),接著改搜尋「vuex」,變成導向「 https://www.google.com/search?q=vuex 」,有發現什麼相同和差別之處了嗎?
$route.query
物件相同的是兩者都在 /search 路由之後出現「?q=」,差別在於後方所接的是各自輸入的搜尋關鍵字,而我們就是要利用「?」來記錄搜尋字詞,問號後方所接的正是「query」查詢字串,其組成通常是一對 key 和 value,假設一段網址最後為「?search=java&user=john」(多組 query 中間以 & 串連) ,則透過 $route.query
便能取得 query 物件的內容為 {search: 'java', user: 'john'}
。
知道方法之後,實際應用在我們的專案範例上:
原本在 <BookList> 元件上監聽的 input 事件是將輸入值直接賦值給 inputText
prop,再傳遞給子元件渲染在 input 上;由於要在監聽事件增加其他執行內容,因此可改為監聽 methods 中的函式 searchBookName
。
<div class="All">
<BookList
navTitle="All"
:bookList="books"
:inputText="searchText"
@searchBook="searchBookName($event)"
/>
<!-- 原先寫法:@searchBook="searchText = $event.target.value" -->
</div>
在searchBookName
函式除了原本的賦值工作,另外追加設置 $router.replace
,讓每次即時輸入字串的同時,都會將輸入值列入 query 物件中,並且更新 key 為 search
的 value 內容,於是每當輸入字串的同時,都會重新導向連動輸入字串的 query 路徑。
methods: {
searchBookName(event) {
this.searchText = event.target.value;
this.$router.replace({
name: this.$route.name,
query: { search: event.target.value },
});
},
},
接著在元件內使用的導航守衛 beforeRouteEnter
進行判斷,當路徑帶有指定 key 為 search
的 query 物件時,要先將 search
對應的 value 賦值給 searchText
,透過 inputText
prop 傳給子元件,因此當重新整理頁面時,趕在畫面渲染好之前,已經將重整頁面之前的搜尋值再次渲染至 input 欄位中了。
這邊需要留意的是,beforeRouteEnter
階段無法取得 this
,因為其會在導航確認之前被調用,當時準備進入的元件實例的尚未完成創建,不過可以在 next
callback 中透過 vm
取得實例,再利用 to
取得目標路由物件中的 query 物件。
beforeRouteEnter(to, from, next) {
next((vm) => {
if (to.query.search) {
vm.searchText = to.query.search;
}
});
},
data() {
return {
searchText: "",
};
},
設置完成後,測試看看在搜尋欄輸入「vue」,檢查一下以下三種情境,無誤的話就代表成功了!
$router.replace
VS $router.push
簡單說明兩者的差別在於 $router.replace
不會向 history 留下紀錄。以上述範例為例,若使用 $router.push
,則當我輸入「java」之後,按下返回上一頁,會使路徑變成「 http://localhost:8080/book/all?search=jav 」;而使用 $router.replace
的話,上一頁會是進入當前路由的上一個頁面。
相關實作也可以應用在登入流程,假設一個使用者的動線是從首頁進入會員登入頁,若登入成功之後應該透過 $router.replace
將他導向會員後台,此時他若想要返回上一頁,就會因為在登入頁沒有留下 history 紀錄,而跳過登入頁又再次回到一開始的進入點首頁,因為對一個登入成功的使用者來說,不適合以登入狀態能夠再次回到登入頁。